/**
 * GameManager.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2010-2012 Roslan Amir. All rights reserved.
 */

package name.roslan.games.moo3d;

import name.roslan.games.moo3d.data.Banner;
import name.roslan.games.moo3d.data.Race;
import name.roslan.games.moo3d.data.Star;
import name.roslan.games.moo3d.spatial.StarHighlight;
import name.roslan.games.moo3d.spatial.StarNode;
import name.roslan.games.moo3d.spatial.StarNodeList;
import name.roslan.games.moo3d.ui.HudManager;
import name.roslan.games.moo3d.utils.HexGridUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ardor3d.framework.Canvas;
import com.ardor3d.input.Key;
import com.ardor3d.input.MouseState;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.LogicalLayer;
import com.ardor3d.input.logical.MouseMovedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.intersection.IntersectionRecord;
import com.ardor3d.intersection.PickData;
import com.ardor3d.intersection.Pickable;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.util.ReadOnlyTimer;

/**
 * This class manages the overall game logic and by delegating to more specialised classes.
 * 
 * @author Roslan Amir
 * @version 1.0 - May 3, 2012
 */
public class GameManager {

	/**
	 * Default no-arg constructor. Private to prevent instatiation.
	 */
	private GameManager() {
	}

    /**
     * Holds the root node of the scene graph.
     */
    public Node rootNode;

	/**
	 * The current screen width.
	 */
	public int screenWidth;

	/**
	 * The current screen height.
	 */
	public int screenHeight;

	/**
	 * The galaxy size index: 0=Small, 1=Medium, 2=large, 3=Huge
	 */
	public int galaxySizeIndex = 0;

    /**
     * The difficulty index: 0=Easy, 1=Normal, 2==Hard
     */
	public int difficultyIndex = 0;

	/**
	 * The width of the galaxy (in tiles) for the current game.
	 */
	public int galaxyWidth;

	/**
	 * The height of the galaxy (in tiles) for the current game.
	 */
	public int galaxyHeight;

	/**
	 * The number of stars in the galaxy for the current game.
	 */
	public int starCount;

	/**
	 * The minimum X coordinate the camera can scroll to.
	 */
	public double minWorldX;

	/**
	 * The maximum X coordinate the camera can scroll to.
	 */
	public double maxWorldX;

	/**
	 * The minimum Y coordinate the camera can scroll to.
	 */
	public double minWorldY;

	/**
	 * The maximum Y coordinate the camera can scroll to.
	 */
	public double maxWorldY;

	/**
	 * Current game state.
	 */
	public GameStates gameState = GameStates.IN_GALAXY_MAP;

	/**
	 * Current user interface action.
	 */
	public Actions action = Actions.NONE;

	/**
	 * The number of races for this game. Default is 2 (player race + one AI race)
	 */
	public int raceCount = 2;

	/**
	 * The array of the player race plus the AI races. The player race is always at index 0.
	 */
	public Race[] races;

	/**
	 * The index into the the various Race arrays representing the selected player race.
	 */
	public int playerRaceIndex = 0;

	/**
	 * The index into the the Race banner arrays representing the selected player race's banner.
	 */
	public int playerBannerIndex = 0;

	/**
	 * The name given to the player race leader.
	 */
	public String playerLeaderName = "Alexander";

	/**
	 * The name given to the player home world.
	 */
	public String playerHomeStarName = "Sol";

	/**
	 * The player race.
	 */
	public Race playerRace;

    /**
     * The star list.
     */
    public final StarNodeList starList = new StarNodeList();

    /**
     * The reference to the player's home star node.
     */
    public StarNode playerHomeStarNode;

    /**
     * The reference to the currently selected star or colony node.
     */
    public StarNode currentStarNode;

    /**
     * The Node for highlighting the currently selected star.
     */
    public StarHighlight starHighlight;

    /**
     * The sigleton instance of this class.
     */
    private static final GameManager instance = new GameManager();

    /**
     * Logger object to handle all logging activities.
     */
    private static final Logger logger = LoggerFactory.getLogger(GameBase.class);

	/**
	 * Working variable.
	 */
	private static final boolean[] raceAssigned = new boolean[Race.RACE_NAMES.length];

	/**
	 * Working variable.
	 */
	private static final boolean[] bannerAssigned = new boolean[Banner.BANNER_NAMES.length];

    /**
     * Holds the instance of the Game class.
     */
    private Game gameInstance;

    /**
     * Returns the singleton instance of this class.
     * 
     * @return the singleton instance of this class
     */
    public static GameManager getInstance() {
        return instance;
    }

    /**
	 * Register the "mouse moved" for actions.
	 * 
	 * @param _logicalLayer
	 */
    public void registerMouseMove(LogicalLayer _logicalLayer) {
		_logicalLayer.registerTrigger(new InputTrigger(new MouseMovedCondition(), new TriggerAction() {
			@Override
			public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					final MouseState mouse = inputStates.getCurrent().getMouseState();
					int x = mouse.getX();
					int y = mouse.getY();
					int dx = mouse.getDx();
					int dy = mouse.getDy();
                    logger.info("mouse moved: x = " + x + ", y = " + y + ", dx = " + dx + ", dy = " + dy);
					if (x <= 10 && dx < 0) {
						logger.info("mouse moved left");
						action = Actions.CAM_LEFT;
					} else if (x >= screenWidth - 10 && dx > 0) {
						logger.info("mouse moved right");
						action = Actions.CAM_RIGHT;
					} else if (y <= 10 && dy < 0) {
						logger.info("mouse moved down");
						action = Actions.CAM_DOWN;
					} else if (y >= screenHeight - 10 && dy > 0) {
						logger.info("mouse moved up");
						action = Actions.CAM_UP;
					}
				}
			}
		}));
	}

	/**
	 * Register the key strokes for for this game.
	 * 
	 * @param _logicalLayer
	 */
	public void registerKeys(LogicalLayer _logicalLayer) {
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.UP), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					action = Actions.CAM_UP;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.LEFT), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					action = Actions.CAM_LEFT;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.RIGHT), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					action = Actions.CAM_RIGHT;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.DOWN), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					action = Actions.CAM_DOWN;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameState == GameStates.IN_GALAXY_MAP) {
					action = Actions.TOGGLE_GRID;
				}
			}
		}));

		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.C), new TriggerAction() {
			@Override
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
                if (gameState == GameStates.IN_GALAXY_MAP) {
                    action = Actions.CENTER_ON_CURRENT_STAR;
                }
			}
		}));

//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.H), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.CENTER_ON_HOME_STAR;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F2), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_NEXT_COLONY;
//				gameState = GameStates.IN_GALAXY_MAP;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F3), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_PREV_COLONY;
//				gameState = GameStates.IN_GALAXY_MAP;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F4), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_NEXT_FLEET;
//				gameState = GameStates.IN_FLEET_INFO;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F5), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_PREV_FLEET;
//				gameState = GameStates.IN_FLEET_INFO;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F6), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_NEXT_STAR;
//				gameState = GameStates.IN_GALAXY_MAP;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F7), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.SHOW_PREV_STAR;
//				gameState = GameStates.IN_GALAXY_MAP;
//			}
//		}));
//		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F12), new TriggerAction() {
//			@Override
//			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
//				action = Actions.DO_NEXT_TURN;
//				gameState = GameStates.IN_TURN_PROCESSING;
//			}
//		}));
	}

	/**
	 * Do any initialization here.
	 * 
	 * @param game - reference to the calling Game (main class) instance.
	 */
	public void initialize(Game game) {
	    gameInstance = game;
		rootNode = game._root;

        /*
         * Initialize the HUD Manager.
         */
        HudManager.getInstance().initialize();

        /*
         * Create the highlight hex grid for highlighting the selected star or colony.
         */
        starHighlight = new StarHighlight();

        /*
         * Starts a new game
         */
		startNewGame();

        /*
         * Set the scoll limits based on the galaxy size.
         */
		HudManager.getInstance().setScrollLimits();

        /*
         * Create the hex grid for the galaxy map.
         */
		HudManager.getInstance().createGrid();

		/*
		 * Center view on the currently selected star.
		 */
		moveStarHighlight(currentStarNode);
        action = Actions.CENTER_ON_CURRENT_STAR;
	}

	/**
	 * Called once per frame.
	 * 
	 * @param timer
	 */
    public void updateGame(ReadOnlyTimer timer) {
//	    double fps = timer.getFrameRate();
//        double tis = timer.getTimeInSeconds();
//        double tpf = timer.getTimePerFrame();
//        long res = timer.getResolution();
//        long time = timer.getTime();
//        logger.info("FrameRate = " + fps);
//        logger.info("TimeInSeconds = " + tis);
//        logger.info("TimePerFrame = " + tpf);
//        logger.info("Resolution = " + res);
//        logger.info("Time = " + time);

		if (action == Actions.NONE)
			return;

		/*
		 * Perform the current action.
		 */
		performAction();
	}

	/**
	 * A <code>Spatial</code> was picked. Now determine what to do with the fact.
	 * 
	 * @param pickData
	 * @param target
	 */
    public void processPick(PickData pickData, Pickable target) {
        /*
         * GameState = IN_STAR_INFO
         */
        if (gameState == GameStates.IN_GALAXY_MAP) {
            /*
             * A star (sphere) was clicked
             */
            if (target instanceof Sphere) {
                Sphere sphere = (Sphere) target;
                logger.info("Target is a Sphere: " + sphere.getName());

                currentStarNode = (StarNode) sphere.getParent();
                logger.info("StarNode: " + currentStarNode.getName());
                action = Actions.SHOW_STAR_INFO;
            }

            /*
             * A fleet (octahedron) was clicked
             */
//            else if (target instanceof Octahedron) {
//                Octahedron fleetMesh = (Octahedron) target;
//                logger.info("Target is a Octahedron: " + fleetMesh.getName());
//                Node fleetParentNode = fleetMesh.getParent();
//                logger.info("Target's parent Node: " + fleetParentNode.getName());
//                if (fleetParentNode instanceof FleetNode) {
//                    currentFleetNode = (FleetNode) fleetParentNode;
//                } else {
//                    currentFleetNode = (FleetNode) fleetParentNode.getParent();
//                }
//                logger.info("FleetNode: " + currentFleetNode.getName());
//                action = Actions.FLEET_PICKED;
//                gameState = GameStates.IN_FLEET_INFO;
//            }

            /*
             * A fleet banner or colony banner or the skybox was clicked
             */
            else if (target instanceof Quad) {
                Quad quad = (Quad) target;
                String quadName = quad.getName();
                logger.info("Target is a Quad: " + quadName);
                /*
                 * Star banner clicked
                 */
//                if (quadName.equals("StarBannerQuad")) {
//                    logger.info("Star banner clicked");
//                    Node starParentNode = quad.getParent();
//                    logger.info("Target's parent Node: " + starParentNode.getName());
//                    if (starParentNode instanceof StarNode) {
//                        currentStarNode = (StarNode) starParentNode;
//                    } else {
//                        currentStarNode = (StarNode) starParentNode.getParent();
//                    }
//                    logger.info("StarNode: " + currentStarNode.getName());
//                    action = Actions.STAR_PICKED;
//                }

                /*
                 * Fleet banner clicked
                 */
//                else if (quadName.equals("FleetBannerQuad")) {
//                    logger.info("Fleet banner clicked");
//                    Node fleetParentNode = quad.getParent();
//                    logger.info("Target's parent Node: " + fleetParentNode.getName());
//                    if (fleetParentNode instanceof FleetNode) {
//                        currentFleetNode = (FleetNode) fleetParentNode;
//                    } else {
//                        currentFleetNode = (FleetNode) fleetParentNode.getParent();
//                    }
//                    logger.info("FleetNode: " + currentFleetNode.getName());
//                    action = Actions.FLEET_PICKED;
//                    gameState = GameStates.IN_FLEET_INFO;
//                }

                /*
                 * Skybox was clicked
                 */
//                else {
                    logger.info("Skybox clicked");
                    Node parentNode = quad.getParent();
                    logger.info("Target's parent Node: " + parentNode.getName());
                    Ray3 ray = pickData.getRay();
                    logger.info("pickData.getRay(): " + ray);
                    logger.info("pickData.getIntersectionRecord(): " + pickData.getIntersectionRecord());
                    logger.info("pickData.getTarget(): " + pickData.getTarget());
                    boolean yep = target.intersectsWorldBound(ray);
                    IntersectionRecord where;
                    logger.info("target.intersectsWorldBound(): " + yep);
                    if (yep) {
                        where = target.intersectsWorldBoundsWhere(ray);
                        logger.info("target.intersectsWorldBoundsWhere(): " + where);
                        logger.info("where.getClosestDistance(): " + where.getClosestDistance());
                        logger.info("where.getClosestIntersection(): " + where.getClosestIntersection());
                        logger.info("where.getFurthestDistance(): " + where.getFurthestDistance());
                        logger.info("where.getFurthestIntersection(): " + where.getFurthestIntersection());
                        logger.info("where.getNumberOfIntersections(): " + where.getNumberOfIntersections());
                        logger.info("where.getIntersectionDistance(): " + where.getIntersectionDistance(0));
                    }
                    where = target.intersectsPrimitivesWhere(ray);
                    logger.info("target.intersectsPrimitivesWhere(): " + where);
                    logger.info("where.getClosestDistance(): " + where.getClosestDistance());
                    logger.info("where.getClosestIntersection(): " + where.getClosestIntersection());
                    logger.info("where.getFurthestDistance(): " + where.getFurthestDistance());
                    logger.info("where.getFurthestIntersection(): " + where.getFurthestIntersection());
                    logger.info("where.getNumberOfIntersections(): " + where.getNumberOfIntersections());
                    logger.info("where.getIntersectionDistance(): " + where.getIntersectionDistance(0));
                    action = Actions.CLOSE_STAR_INFO;
//                }
            }

            /*
             * A fleet model was clicked
             */
//            else if (target instanceof Mesh) {
//                Mesh fleetMesh = (Mesh) target;
//                logger.info("Target is a Mesh: " + fleetMesh.getName());
//                Node fleetParentNode = fleetMesh.getParent();
//                logger.info("Target's parent Node: " + fleetParentNode.getName());
//                if (fleetParentNode instanceof FleetNode) {
//                    currentFleetNode = (FleetNode) fleetParentNode;
//                } else {
//                    currentFleetNode = (FleetNode) fleetParentNode.getParent();
//                }
//                logger.info("FleetNode: " + currentFleetNode.getName());
//                action = Actions.FLEET_PICKED;
//                gameState = GameStates.IN_FLEET_INFO;
//            }

            /*
             * Another spatial was clicked. Close the 
             */
            else {
                logger.info("Target is an unknown: " + target);
                action = Actions.CLOSE_STAR_INFO;
            }
        }

//        /*
//         * GameState = IN_FLEET_INFO
//         */
//        else if (gameState == GameStates.IN_FLEET_INFO) {
//            /*
//             * A star was clicked
//             */
//            if (target instanceof Sphere) {
//                Sphere star = (Sphere) target;
//                logger.info("Target is a Star: " + star.getName());
//                StarNode starNode = (StarNode) star.getParent();
//                if (starNode != currentDestination) {
//                    currentDestination = starNode;
//                    action = Actions.DESTINATION_PICKED;
//                }
//            }
//
//            /*
//             * A fleet banner or colony banner or the skybox was clicked
//             */
//            else if (target instanceof Quad) {
//                Quad quad = (Quad) target;
//                String quadName = quad.getName();
//                logger.info("Target is a Quad: " + quadName);
//
//                /*
//                 * Star
//                 */
//                if (quadName.equals("StarBannerQuad")) {
//                    StarNode starNode = (StarNode) quad.getParent();
//                    if (starNode != currentDestination) {
//                        currentDestination = starNode;
//                        action = Actions.DESTINATION_PICKED;
//                    }
//                }
//
//                /*
//                 * Fleet
//                 */
//                else if (quadName.equals("FleetBannerQuad")) {
//                    Node fleetParentNode = quad.getParent();
//                    logger.info("Target's parent Node: " + fleetParentNode.getName());
//                    FleetNode fleetNode;
//                    if (fleetParentNode instanceof FleetNode) {
//                        fleetNode = (FleetNode) fleetParentNode;
//                    } else {
//                        fleetNode = (FleetNode) fleetParentNode.getParent();
//                    }
//                    logger.info("FleetNode: " + fleetNode.getName());
//                    if (fleetNode != currentFleetNode) {
//                        currentFleetNode = fleetNode;
//                        currentDestination = null;
//                        action = Actions.FLEET_PICKED;
//                    }
//                }
//
//                /*
//                 * Skybox
//                 */
//                else {
//                    Node parentNode = quad.getParent();
//                    logger.info("Target's parent Node: " + parentNode.getName());
//                }
//            }
//
//            /*
//             * A fleet model was clicked
//             */
//            else if (target instanceof Mesh) {
//                Mesh fleetMesh = (Mesh) target;
//                logger.info("Target is a Fleet Mesh: " + fleetMesh.getName());
//                Node fleetParentNode = fleetMesh.getParent();
//                logger.info("Target's parent Node: " + fleetParentNode.getName());
//                FleetNode fleetNode;
//                if (fleetParentNode instanceof FleetNode) {
//                    fleetNode = (FleetNode) fleetParentNode;
//                } else {
//                    fleetNode = (FleetNode) fleetParentNode.getParent();
//                }
//                logger.info("FleetNode: " + fleetNode.getName());
//                if (fleetNode != currentFleetNode) {
//                    currentFleetNode = fleetNode;
//                    currentDestination = null;
//                    action = Actions.FLEET_PICKED;
//                }
//            }
//
//            /*
//             * An unknown spatial was clicked
//             */
//            else {
//                logger.info("Target is an unknown: " + target);
//                currentDestination = null;
//            }
//        }

        /*
         * GameState = OTHERS
         */
        else {
            logger.info("Target is ignored: " + target);
        }
    }

	// ==========================
	// Private Methods
	// ==========================

    /**
     * Starts a new game with the selected galaxy size, difficulty, number of opponents, and the selected race and banner color.
     */
    private void startNewGame() {
        switch (GameManager.getInstance().galaxySizeIndex) {
        case 0: // small (orig: 213x180)
            GameManager.getInstance().galaxyWidth = 24;
            GameManager.getInstance().galaxyHeight = 24;
            GameManager.getInstance().starCount = 24;
            break;
        case 1: // medium (orig: 273x243)
            GameManager.getInstance().galaxyWidth = 30;
            GameManager.getInstance().galaxyHeight = 30;
            GameManager.getInstance().starCount = 48;
            break;
        case 2: // large (orig: 333x278)
            GameManager.getInstance().galaxyWidth = 36;
            GameManager.getInstance().galaxyHeight = 36;
            GameManager.getInstance().starCount = 72;
            break;
        default: // huge (orig: 389x338)
            GameManager.getInstance().galaxyWidth = 42;
            GameManager.getInstance().galaxyHeight = 42;
            GameManager.getInstance().starCount = 108;
        }

        /*
         * Create the player and AI races.
         */
        createRaces();

        /*
         * Create the stars in the game.
         */
        createStars();        
    }

    /**
     * Move the position of the highlight to the specified <code>StarNode</code>.
     *
     * @param starNode - the <code>StarNode</code> to move the highlight to
     */
    private void moveStarHighlight(StarNode starNode) {
        if (!GameManager.getInstance().rootNode.hasChild(GameManager.getInstance().starHighlight)) {
//          GameManager.getInstance().rootNode.detachChild(fleetHighlight);
            GameManager.getInstance().rootNode.attachChild(GameManager.getInstance().starHighlight);
        }
        starHighlight.setTranslation(starNode.getTranslation());
    }

	/**
	 * Create a the array of selected player race plus the random opponents.
	 */
	private void createRaces() {
		/*
		 * Create the array of races (the player's race is at index 0).
		 */
		races = new Race[raceCount];

		/*
		 * Create the player's selected race.
		 */
		playerRace = races[0] = createPlayerRace();
		logger.info("Player Race Index: " + playerRaceIndex);
		logger.info("Player Race Name: " + Race.RACE_NAMES[playerRaceIndex]);
		logger.info("Player Race Description: " + Race.RACE_DESCRIPTIONS[playerRaceIndex]);
		logger.info("Player Leader Name: " + playerLeaderName);
		logger.info("Player Banner: " + Banner.BANNER_NAMES[playerBannerIndex]);
		logger.info("Player Home Star: " + playerHomeStarName);

		/*
		 * Create the AI's races.
		 */
		for (int index = 1; index < races.length; index++) {
			races[index] = createRandomRace();
			logger.info("AI Race Index: " + index);
			logger.info("AI Race Name: " + Race.RACE_NAMES[index]);
			logger.info("AI Race Description: " + Race.RACE_DESCRIPTIONS[index]);
			logger.info("AI Leader Name: " + races[index].leaderName);
			logger.info("AI Banner: " + Banner.BANNER_NAMES[races[index].bannerIndex]);
			logger.info("AI Home Star: " + Star.STAR_NAMES[index]);
		}
	}

	/**
	 * Create the player <code>Race</code> object.
	 *
	 * @return the <code>Race</code> object
	 */
	private Race createPlayerRace() {
		raceAssigned[playerRaceIndex] = true;
		bannerAssigned[playerBannerIndex] = true;
		return new Race(playerRaceIndex, playerLeaderName, playerBannerIndex);
	}

	/**
	 * Create a random AI player race.
	 * 
	 * @return the <code>Race</code> object
	 */
	private Race createRandomRace() {
		int raceIndex = MathUtils.nextRandomInt(0, Race.RACE_NAMES.length - 1);
		while (raceAssigned[raceIndex]) {
			if (++raceIndex == Race.RACE_NAMES.length)
				raceIndex = 0;
		}
		raceAssigned[raceIndex] = true;

		int bannerIndex = MathUtils.nextRandomInt(0, Banner.BANNER_NAMES.length - 1);
		while (bannerAssigned[bannerIndex]) {
			if (++bannerIndex == Banner.BANNER_NAMES.length)
				bannerIndex = 0;
		}
		bannerAssigned[bannerIndex] = true;

		return new Race(raceIndex, null, bannerIndex);
	}

    /**
     * Create all the stars in the galaxy. The first star created is Orion. Then the player's home star, followed by each of the AI players' home
     * stars. Then the remaining unexplored stars are created.
     */
	private void createStars() {
		/*
		 * Create the list of stars based on the galaxy size.
		 */
		starList.clear();
		starList.ensureCapacity(starCount);

		/*
		 * Delegate the creation of the initial stars and home worlds to the StarManager class.
		 */
		StarManager.getInstance().createStars();

		/*
		 * The currently selected star is the player's home world.
		 */
		currentStarNode = playerHomeStarNode;
	}

	/**
	 * Perform the current action. 
	 */
	private void performAction() {
		switch (action) {
		case CAM_UP:
			Vector3 point = Game.control.getLookAtPoint();
			if (point.getY() < maxWorldY) {
				point.addLocal(0, HexGridUtils.HEXGRID_S + HexGridUtils.HEXGRID_H, 0);
				Game.control.setLookAtPoint(point);
				Game.control.makeDirty();
			}
			action = Actions.NONE;
			break;
		case CAM_LEFT:
			point = Game.control.getLookAtPoint();
			if (point.getX() > minWorldX) {
				point.addLocal(-(HexGridUtils.HEXGRID_R * 2), 0, 0);
				Game.control.setLookAtPoint(point);
				Game.control.makeDirty();
			}
			action = Actions.NONE;
			break;
		case CAM_RIGHT:
			point = Game.control.getLookAtPoint();
			if (point.getX() < maxWorldX) {
				point.addLocal(HexGridUtils.HEXGRID_R * 2, 0, 0);
				Game.control.setLookAtPoint(point);
				Game.control.makeDirty();
			}
			action = Actions.NONE;
			break;
		case CAM_DOWN:
			point = Game.control.getLookAtPoint();
			if (point.getY() > minWorldY) {
				point.addLocal(0, -(HexGridUtils.HEXGRID_S + HexGridUtils.HEXGRID_H), 0);
				Game.control.setLookAtPoint(point);
				Game.control.makeDirty();
			}
			action = Actions.NONE;
			break;
		case TOGGLE_GRID:
		    HudManager.getInstance().toggleGrid();
			action = Actions.NONE;
			break;
        case CENTER_ON_CURRENT_STAR:
            centerOnStar();
            action = Actions.NONE;
            break;
        case SHOW_STAR_INFO:
//          moveStarHighlight();
            HudManager.getInstance().showStarInfo();
            action = Actions.NONE;
            break;
        case CLOSE_STAR_INFO:
//          hideStarHighlight();
//          HudManager.getInstance().closeStarInfo();
            action = Actions.NONE;
            break;
        case EXIT_GAME:
            gameInstance.exit();
            break;
		default:
			action = Actions.NONE;
		}
	}

    /**
     * Center the currently selected star on the screen.
     */
    private void centerOnStar() {
        Game.control.setLookAtSpatial(starHighlight);
    }

	// ==========================
	// Global Enums and variables
	// ==========================

    /**
	 * Game states.
	 */
	public enum GameStates {
		IN_GALAXY_MAP
	}

	/**
	 * Game actions.
	 */
	public enum Actions {
		NONE, CAM_UP, CAM_DOWN, CAM_LEFT, CAM_RIGHT, TOGGLE_GRID, CENTER_ON_CURRENT_STAR, EXIT_GAME, SHOW_STAR_INFO, CLOSE_STAR_INFO
	}

}
